package com.xwq.xwqaiagent.chatmemory;

// 序列化相关依赖
import com.esotericsoftware.kryo.Kryo;           // Kryo序列化引擎
import com.esotericsoftware.kryo.io.Input;      // 用于读取序列化数据
import com.esotericsoftware.kryo.io.Output;     // 用于写入序列化数据
import org.objenesis.strategy.StdInstantiatorStrategy;  // 绕过构造函数的实例化策略

// Spring AI组件
import org.springframework.ai.chat.memory.ChatMemory;  // 聊天记忆接口
import org.springframework.ai.chat.messages.Message;   // 消息实体

// Java IO
import java.io.File;             // 文件操作
import java.io.FileInputStream;  // 文件输入
import java.io.FileOutputStream; // 文件输出
import java.io.IOException;      // IO异常

// 集合框架
import java.util.ArrayList;       // 动态数组
import java.util.List;            // 列表接口

/**
 * 基于文件存储的对话记忆实现
 * 使用Kryo序列化框架将对话记录持久化到本地文件
 */
public class FileBasedChatMemory implements ChatMemory {

    // 文件存储根目录
    private final String BASE_DIR;

    // Kryo实例（注意：静态成员在多线程环境下存在线程安全问题）
    private static final Kryo kryo = new Kryo();

    // 静态初始化块：配置Kryo序列化器
    static {
        kryo.setRegistrationRequired(false);  // 关闭类注册验证
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); // 设置绕过构造函数的实例化策略
    }

    /**
     * 构造方法：初始化存储目录
     * @param dir 对话记录存储目录路径
     */
    public FileBasedChatMemory(String dir) {
        this.BASE_DIR = dir;
        File baseDir = new File(dir);
        if (!baseDir.exists()) {
            baseDir.mkdirs();  // 创建多级目录（如果不存在）
        }
    }

    /**
     * 添加消息到指定对话
     * @param conversationId 对话ID（作为文件名）
     * @param messages 要添加的消息列表
     * （注意：多个线程同时操作同一会话可能导致数据覆盖）
     */
    @Override
    public void add(String conversationId, List<Message> messages) {
        // 获取或创建对话记录
        List<Message> conversationMessages = getOrCreateConversation(conversationId);
        // 添加新消息
        conversationMessages.addAll(messages);
        // 持久化到文件
        saveConversation(conversationId, conversationMessages);
    }

    /**
     * 获取最近N条对话记录
     * @param conversationId 对话ID
     * @param lastN 要获取的消息数量
     * @return 最近N条消息的不可变列表
     */
    @Override
    public List<Message> get(String conversationId, int lastN) {
        List<Message> allMessages = getOrCreateConversation(conversationId);
        // 使用流处理获取最后N条记录
        return allMessages.stream()
                .skip(Math.max(0, allMessages.size() - lastN))
                .toList();  // 生成不可变列表
    }

    /**
     * 清除指定对话记录
     * @param conversationId 要清除的对话ID
     */
    @Override
    public void clear(String conversationId) {
        File file = getConversationFile(conversationId);
        if (file.exists()) {
            file.delete();  // 直接删除数据文件
        }
    }

    //------------------------ 辅助方法 ------------------------

    /**
     * 获取或创建对话记录（内部方法）
     * @param conversationId 对话ID
     * @return 消息列表（注意：反序列化时可能丢失泛型类型信息）
     */
    private List<Message> getOrCreateConversation(String conversationId) {
        File file = getConversationFile(conversationId);
        List<Message> messages = new ArrayList<>();

        if (file.exists()) {
            try (Input input = new Input(new FileInputStream(file))) {
                // 从文件加载已存在的对话记录
                // 注意：这里直接读取ArrayList.class可能产生类型安全问题
                messages = kryo.readObject(input, ArrayList.class);
            } catch (IOException e) {
                e.printStackTrace();  // 简单打印异常（建议记录日志）
            }
        }
        return messages;
    }

    /**
     * 保存对话记录到文件（内部方法）
     * @param conversationId 对话ID
     * @param messages 要保存的消息列表
     * （注意：写入过程若中断可能导致数据损坏）
     */
    private void saveConversation(String conversationId, List<Message> messages) {
        File file = getConversationFile(conversationId);
        try (Output output = new Output(new FileOutputStream(file))) {
            kryo.writeObject(output, messages);  // 序列化写入文件
        } catch (IOException e) {
            e.printStackTrace();  // 简单打印异常（建议记录日志）
        }
    }

    /**
     * 生成对话文件路径（内部方法）
     * @param conversationId 对话ID
     * @return 对应的.kryo文件对象
     * （注意：未对conversationId做安全过滤，可能产生路径注入问题）
     */
    private File getConversationFile(String conversationId) {
        return new File(BASE_DIR, conversationId + ".kryo");
    }
}